package cn.stu.cms.service.impl;


import cn.hutool.core.util.StrUtil;
import cn.stu.cms.dto.FileDto;
import cn.stu.cms.mapper.FileMapper;
import cn.stu.cms.pojo.File;
import cn.stu.cms.service.FileService;
import cn.stu.cms.util.R;
import cn.stu.cms.util.UserHolder;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import io.minio.*;
import io.minio.errors.ErrorResponseException;
import io.minio.errors.InsufficientDataException;
import io.minio.errors.InvalidResponseException;
import io.minio.errors.XmlParserException;
import io.minio.messages.Item;
import jdk.nashorn.internal.runtime.regexp.joni.exception.InternalException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.rmi.ServerException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Objects;


/**
 * 文件服务实施
 * 文件服务实现类
 *
 * @author gh
 * @date 2023/09/14
 */
@Service
public class FileServiceImpl extends ServiceImpl<FileMapper, File> implements FileService {

    /**
     * Mino客户端
     */
    @Resource
    private MinioClient minioClient;

    /**
     * 存储桶名称
     */
    @Value("${minio.file-bucket-name}")
    private String bucketName;


    /**
     * 按实体列出
     *
     * @param pageNumber 页码
     * @param pageSize   页面大小
     * @param dto        DTO
     * @return {@link R}
     */
    @Override
    public R<Page<File>> listByEntity(int pageNumber, int pageSize, FileDto dto) {
        Page<File> page = new Page<>(pageNumber, pageSize);
        lambdaQuery()
                .like(StrUtil.isNotBlank(dto.getName()), File::getName, dto.getName())
                .eq(dto.getUserId() == null, File::getShared, dto.getShared())
                .eq(dto.getUserId() != null, File::getUserId, dto.getUserId())
                .eq(dto.getParentId() != null, File::getParentId, dto.getParentId())
                .page(page);
        return R.ok(page, "获取成功");
    }

    /**
     * 创建
     *
     * @param multipartFile 多部分文件
     * @param file          文件
     * @return {@link R}
     * @throws IOException               IOEXCEPTION
     * @throws InvalidKeyException       无效键异常
     * @throws InvalidResponseException  无效响应异常
     * @throws InsufficientDataException 数据不足异常
     * @throws NoSuchAlgorithmException  没有这样算法异常
     * @throws ServerException           服务器异常
     * @throws InternalException         内部异常
     * @throws XmlParserException        XML解析器异常
     * @throws ErrorResponseException    错误响应异常
     * @throws ServerException           服务器异常
     * @throws InternalException         内部异常
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public R<File> create(MultipartFile multipartFile, File file) throws IOException, InvalidKeyException, InvalidResponseException,
            InsufficientDataException, NoSuchAlgorithmException, InternalException, XmlParserException, ErrorResponseException, io.minio.errors.ServerException, io.minio.errors.InternalException {
        file.setUserId(UserHolder.getUser().getUserId());
        if (multipartFile == null) {
            Assert.notNull(file.getName(), "文件夹名不能为空");
            file.setType("文件夹");
            file.setSize("-");
        } else {
            boolean isBucketExist = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
            if (!isBucketExist) {
                minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
            }
            String fileName = multipartFile.getOriginalFilename();
            int dotIndex = Objects.requireNonNull(fileName).lastIndexOf('.');
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("/yyyy/MM/dd/");
            String filePath = file.getUserId() + LocalDate.now().format(formatter) + System.currentTimeMillis() + fileName.substring(dotIndex);
            file.setName(fileName);
            file.setPath(filePath);
            file.setType(fileName.substring(dotIndex + 1));
            file.setSize(formatSize(multipartFile.getSize()));
            file.setContentType(multipartFile.getContentType());
            minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(filePath)
                    .stream(multipartFile.getInputStream(), multipartFile.getSize(), -1)
                    .contentType(multipartFile.getContentType()).build());
        }
        file.setShared(false);
        baseMapper.insert(file);
        return R.ok(file, "操作成功");
    }


    /**
     * 通过ID获取
     *
     * @param id       ID
     * @param response 响应
     * @return {@link R}
     * @throws IOException               IOEXCEPTION
     * @throws InvalidKeyException       无效键异常
     * @throws InvalidResponseException  无效响应异常
     * @throws InsufficientDataException 数据不足异常
     * @throws NoSuchAlgorithmException  没有这样算法异常
     * @throws ServerException           服务器异常
     * @throws InternalException         内部异常
     * @throws XmlParserException        XML解析器异常
     * @throws ErrorResponseException    错误响应异常
     * @throws ServerException           服务器异常
     * @throws InternalException         内部异常
     */
    @Override
    public R<String> getById(Long id, HttpServletResponse response) throws IOException, InvalidKeyException, InvalidResponseException,
            InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, ErrorResponseException, io.minio.errors.ServerException, io.minio.errors.InternalException {
        File file = baseMapper.selectById(id);
        if (file == null) {
            return null;
        }
        response.setContentType("application/octet-stream");
        response.setHeader("content-type", file.getContentType());
        String fileName = URLEncoder.encode(file.getName(), "UTF-8");
        response.setHeader("Content-Disposition", "attachment;" + "filename=" + fileName.replace("+", "%20"));
        try (InputStream in = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(file.getPath()).build());
             OutputStream out = response.getOutputStream()) {
            int len;
            byte[] buffer = new byte[1024];
            while ((len = in.read(buffer)) > 0) {
                out.write(buffer, 0, len);
            }
        } catch (IOException e) {
            response.setStatus(406);
            log.error(e.getMessage());
            return R.failed("系统异常，请联系管理员");
        }
        return R.ok("下载成功");
    }

    /**
     * 更新
     *
     * @param file 文件
     * @return {@link R}
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public R<File> update(File file) {
        Long userId = UserHolder.getUser().getUserId();
        if (file.getName() != null && file.getName().length() >= 30) {
            return R.failed("文件名过长");
        }
        file.setUserId(userId);
        baseMapper.updateById(file);
        File f = baseMapper.selectById(file.getId());
        return R.ok(f, "修改成功");
    }

    /**
     * 按ID删除
     *
     * @param id ID
     * @return {@link R}
     * @throws IOException               IOEXCEPTION
     * @throws InvalidKeyException       无效键异常
     * @throws InvalidResponseException  无效响应异常
     * @throws InsufficientDataException 数据不足异常
     * @throws NoSuchAlgorithmException  没有这样算法异常
     * @throws ServerException           服务器异常
     * @throws InternalException         内部异常
     * @throws XmlParserException        XML解析器异常
     * @throws ErrorResponseException    错误响应异常
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public R<String> deleteById(Long id) throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException,
            NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, ErrorResponseException {
        File file = baseMapper.selectById(id);
        if (file == null) {
            return R.failed("文件不存在");
        }
        try {
            //是文件夹
            if (file.isFolder()) {
                if (StrUtil.isBlank(file.getPath())) {
                    baseMapper.deleteById(file.getId());
                } else {
                    // 处理删除非空文件夹
                    deleteFolderRecursive(file.getPath());
                }
            } else {
                // 删除单个文件
                minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(file.getPath()).build());
            }
        } catch (Exception e) {
            log.error(e.getMessage());
            return R.failed("error");
        }
        baseMapper.deleteById(id);
        return R.ok("success");
    }

    /**
     * 删除递归文件夹
     *
     * @param folderPath 文件夹路径
     * @throws IOException               IOEXCEPTION
     * @throws InvalidKeyException       无效键异常
     * @throws InvalidResponseException  无效响应异常
     * @throws InsufficientDataException 数据不足异常
     * @throws NoSuchAlgorithmException  没有这样算法异常
     * @throws ServerException           服务器异常
     * @throws InternalException         内部异常
     * @throws XmlParserException        XML解析器异常
     * @throws ErrorResponseException    错误响应异常
     * @throws ServerException           服务器异常
     * @throws InternalException         内部异常
     */
    private void deleteFolderRecursive(String folderPath) throws IOException, InvalidKeyException,
            InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException,
            InternalException, XmlParserException, ErrorResponseException, io.minio.errors.ServerException, io.minio.errors.InternalException {
        // 获取文件夹内的所有文件和子文件夹
        Iterable<Result<Item>> objects = minioClient.listObjects(ListObjectsArgs.builder()
                .bucket(bucketName)
                .prefix(folderPath)
                .recursive(true)
                .build());
        // 逐个删除文件和子文件夹
        for (Result<Item> result : objects) {
            Item item = result.get();
            if (!item.isDir()) {
                // 删除文件
                minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(item.objectName()).build());
            } else {
                // 递归删除子文件夹
                deleteFolderRecursive(item.objectName());
            }
        }
        // 删除空文件夹
        minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(folderPath).build());
    }

    /**
     * 格式大小
     *
     * @param size 大小
     * @return {@link String}
     */
    public String formatSize(long size) {
        if (size < 1024) {
            return size + "B";
        } else if (size < 1048576) {
            return (size >> 10) + "KB";
        } else {
            return (size >> 20) + "MB";
        }
    }

}